﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.SharePoint.Taxonomy;
using Microsoft.SharePoint;
using System.Xml;
using System.Globalization;
using System.Management.Automation;

namespace SharePoint.Utilities.Cmdlets
{
    public static class ImportTermStoreHelper
    {

        private static TermStore termStore = null;
        private static SPSite siteCollection;
        public static string termStoreName;
        public static string sourceEndPoint;
        public static string destinationEndPoint;

        /// <summary>
        /// Get all term sets in the term store for the site column
        /// </summary>
        /// <param name="el"></param>
        /// <param name="sourceEndPoint"></param>
        /// <returns></returns>
        private static XElement GetTermsFromTermStore(XElement el, string sourceEndPoint)
        {

            XNode xnSspIds = new XElement("sspIds", new XElement("sspId", new Guid(GetTaxonomyFieldValue(el, "SspId"))));
            XNode xnTermSetIds = new XElement("termSetIds", new XElement("termSetId", new Guid(GetTaxonomyFieldValue(el, "TermSetId"))));

            XNode xnDateTimes = new XElement("dateTimes", new XElement("dateTime", (new DateTime(1900, 1, 1)).ToString("s")));
            XNode xnVersions = new XElement("versions", new XElement("version", 0));

            ShowMessage(string.Format("Retrieving Terms from site {0}", sourceEndPoint));
            return XElement.Parse(ProxyHelper.TaxonomyChannelFactory(sourceEndPoint).GetTermSets(new GetTermSetsRequest(
                 new GetTermSetsRequestBody(
                     xnSspIds.ToString(SaveOptions.DisableFormatting),
                     xnTermSetIds.ToString(SaveOptions.DisableFormatting),
                     CultureInfo.GetCultureInfo("en-US").LCID,
                     xnDateTimes.ToString(SaveOptions.DisableFormatting),
                      xnVersions.ToString(SaveOptions.DisableFormatting))
                 )).Body.GetTermSetsResult);


        }

        private static string GetTaxonomyFieldValue(XElement PropertyElement, string PropertyName)
        {
            XNamespace ns = PropertyElement.Name.Namespace;
            foreach (var property in PropertyElement.Elements(ns + "Customization").Descendants().First().Elements())
            {
                if (property.Elements(ns + "Name").Single().Value == PropertyName)
                {
                    return property.Elements(ns + "Value").Single().Value;
                }


            }

            return "";

        }

        /// <summary>
        /// Function to get term store from teh taxonomy session for the destination site colleaction
        /// </summary>
        /// <param name="siteCollectionUrl"></param>
        /// <returns></returns>
        public static TermStore GetTermStoreForSite(string siteCollectionUrl)
        {
            ShowMessage(string.Format("Retrieving TermStore for Site {0}", siteCollectionUrl));
            using (siteCollection = new SPSite(siteCollectionUrl))
            {
                TaxonomySession txnSession = new TaxonomySession(siteCollection);
                termStore = txnSession.TermStores[termStoreName];
                return termStore;
            }
        }

        /// <summary>
        /// Function to import terms store
        /// This function needs to be run from teh server where the content needs to be imported.
        /// </summary>
        public static string ImportTermStore()
        {
            string infoMessages = string.Empty;
            // Get term store specified in input for the destination site collection
            GetTermStoreForSite(destinationEndPoint);
            infoMessages += "Retrieved TermStore for Site " + destinationEndPoint + "<br/>";
            ShowMessage(string.Format("Retrieving Site Columns from site {0}", sourceEndPoint));
            infoMessages += string.Format("Retrieving Site Columns from site {0}", sourceEndPoint) + "<br/>";
            // Get all site columns fro source site collection
            var siteColumns = ProxyHelper.WebsChannelFactory(sourceEndPoint).GetColumns(new GetColumnsRequest()).Body.GetColumnsResult.ToXElement(); ;
            // Get all taxonomy fields
            var txnfields = siteColumns.Elements().Where(x => x.Attribute("Type").Value == "TaxonomyFieldType");
            // For each taxonomy field, import the terms in the term set from source to destination
            foreach (var el in txnfields)
            {
                infoMessages += ImportTerms(GetTermsFromTermStore(el, sourceEndPoint));
            }

            // For each column(taxonomy field) update the SspId with the new term store id
            for (int i = 0; i <= siteCollection.RootWeb.Fields.Count - 1; i++)
            {
                SPField field = siteCollection.RootWeb.Fields[i];
                if (field.GetType().Name == "TaxonomyField")
                {
                    TaxonomyField taxField = (TaxonomyField)field;
                    taxField.SspId = termStore.Id;
                    taxField.Update();
                }
            }

            return infoMessages;
        }

        /// <summary>
        /// Imports terms in a perticular term set
        /// </summary>
        /// <param name="sourceTermStore"></param>
        private static string ImportTerms(XElement sourceTermStore)
        {
            string infoMessages = string.Empty;
            // Create term set and get the reference
            TermSet currTermSet = GetTermSetToUpdate(sourceTermStore);
            foreach (var el in sourceTermStore.Elements().Where(x => x.Name == "TermStore").Elements().Where(x => x.Name == "T"))
            {
                string parentGuid = "none";

                if (el.Element("TMS").Element("TM").Attribute("a25") != null)
                {
                    parentGuid = el.Element("TMS").Element("TM").Attribute("a25").Value;
                    AppendChildTerms(sourceTermStore, currTermSet, el, parentGuid);
                }
                else
                {
                    currTermSet.CreateTerm(el.Element("LS").Element("TL").Attribute("a32").Value,
                        CultureInfo.GetCultureInfo("en-US").LCID, new Guid(el.Attribute("a9").Value));

                    ShowMessage(string.Format("Importing Top Level Term : {0}", el.Element("LS").Element("TL").Attribute("a32").Value));
                    infoMessages += string.Format("Importing Top Level Term : {0}", el.Element("LS").Element("TL").Attribute("a32").Value) + "<br/>";
                    termStore.CommitAll();
                }
            }
            return infoMessages;

        }

        private static void AppendChildTerms(XElement sourceElement, Term Term, TermSet termSet)
        {

            foreach (var el in sourceElement.Elements().Where(x => x.Name == "TermStore").Elements().Where(x => x.Name == "T"))
            {
                foreach (Term term in Term.Terms)
                {

                    if (el.Element("TMS").Element("TM").Attribute("a25") != null && term.Id == new Guid(el.Element("TMS").Element("TM").Attribute("a25").Value))
                    {

                        if (!CheckIfTermExists(term, new Guid(el.Attribute("a9").Value)))
                        {
                            Term newTerm = term.CreateTerm(el.Element("LS").Element("TL").Attribute("a32").Value, CultureInfo.GetCultureInfo("en-US").LCID,
                                                new Guid(el.Attribute("a9").Value));

                            ShowMessage(string.Format("Importing : {0}", el.Element("LS").Element("TL").Attribute("a32").Value));


                            termStore.CommitAll();
                            AppendChildTerms(sourceElement, term, termSet);

                        }

                    }

                }
            }

        }


        /// <summary>
        /// Append child term to the current term
        /// </summary>
        /// <param name="sourceElement"></param>
        /// <param name="currTermSet"></param>
        /// <param name="currElement"></param>
        /// <param name="parentGuid"></param>
        private static void AppendChildTerms(XElement sourceElement, TermSet currTermSet, XElement currElement, string parentGuid)
        {


            foreach (Term term in currTermSet.Terms)
            {

                if (term.Id == new Guid(currElement.Element("TMS").Element("TM").Attribute("a25").Value))
                {
                    term.CreateTerm(currElement.Element("LS").Element("TL").Attribute("a32").Value, CultureInfo.GetCultureInfo("en-US").LCID,
                    new Guid(currElement.Attribute("a9").Value));


                    ShowMessage(string.Format("Importing Term Group : {0}", currElement.Element("LS").Element("TL").Attribute("a32").Value));


                    termStore.CommitAll();


                    AppendChildTerms(sourceElement, term, currTermSet);

                }

            }

        }



        private static bool CheckIfTermExists(Term term, Guid TermGuid)
        {
            foreach (Term currTerm in term.Terms)
            {

                if (currTerm.Id == TermGuid)
                {
                    return true;

                }

            }
            return false;
        }

        /// <summary>
        /// Get term set to update from the term store
        /// </summary>
        /// <param name="sourceTermStore"></param>
        /// <returns></returns>
        private static TermSet GetTermSetToUpdate(XElement sourceTermStore)
        {
            string TermStoreGroupSuffix = " Group";
            string termSetGuid = sourceTermStore.Elements().Where(x => x.Name == "TermStore").Elements().Where(x => x.Name == "TS").Single().Attribute("a9").Value;
            string termSetName = sourceTermStore.Elements().Where(x => x.Name == "TermStore").Elements().Where(x => x.Name == "TS").Single().Attribute("a12").Value;
            // Delete all groups and term sets in the group if they already exists
            if (termStore.Groups.Where(x => x.Name == termSetName + TermStoreGroupSuffix).Count() > 0)
            {
                foreach (var termset in termStore.Groups.Where(x => x.Name == termSetName + TermStoreGroupSuffix).Single().TermSets)
                {
                    termset.Delete();
                    ShowMessage(termSetName + " - TermSet Deleted");
                }

                termStore.Groups.Where(x => x.Name == termSetName + TermStoreGroupSuffix).Single().Delete();
                termStore.CommitAll();
                ShowMessage(termSetName + TermStoreGroupSuffix + " - Group Deleted");

            }

            // Create the group
            termStore.CreateGroup(termSetName + TermStoreGroupSuffix);
            termStore.CommitAll();

            ShowMessage(termSetName + " - Group Created");



            Group group = termStore.Groups[termSetName + TermStoreGroupSuffix];
            // Create term sets in the group
            if (group.TermSets.Select(x => x.Name == termSetName).Count() == 0)
            {
                group.CreateTermSet(termSetName, new Guid(termSetGuid));
            }

            return group.TermSets[termSetName];

        }

        public static XElement ToXElement(this XmlElement xml)
        {
            XmlDocument doc = new XmlDocument();
            doc.AppendChild(doc.ImportNode(xml, true));
            return XElement.Parse(doc.InnerXml);
        }


        public static void ShowMessage(string Message)
        {

            Console.WriteLine(DateTime.Now.ToString() + ":" + Message);



        }
    }
}
